package org.shj.spring.boot.lang;

import com.esotericsoftware.reflectasm.MethodAccess;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import org.apache.commons.lang3.StringUtils;
import org.shj.spring.boot.lang.util.ClassUtil;
import org.shj.spring.boot.lang.util.ReflectionUtil;

import java.lang.reflect.Field;

/**
 * Created by xuwei on 2017/3/29.
 */
public class BeanCopier {

    private static Table<Class, Class, BeanCopier> beanCopierTable = HashBasedTable.create();

    private Class srcClass;
    private Class targetClass;

    private BeanCopier(Class srcClass, Class targetClass) {
        this.srcClass = srcClass;
        this.targetClass = targetClass;
    }

    /**
     * 根据srcClass,targetClass缓存获取BeanCopier.
     *
     * @param srcClass    源类型
     * @param targetClass 目标类型
     * @return BeanCopier
     */
    public static BeanCopier getBeanCopier(Class srcClass, Class targetClass) {
        BeanCopier beanCopier = beanCopierTable.get(srcClass, targetClass);
        if (beanCopier == null) {
            beanCopier = new BeanCopier(srcClass, targetClass);
            beanCopierTable.put(srcClass, targetClass, beanCopier);
        }
        return beanCopier;
    }

    /**
     * 拷贝数据
     *
     * @param source    源实体
     * @param target    目标实体
     * @param converter 默认转换类
     */
    public void copy(Object source, Object target, Converter converter) {
        if (source == null || target == null || converter == null) {
            return;
        }
        MethodAccess srcMethodAccess = ReflectionUtil.getMethodAccess(source.getClass());
        MethodAccess targetMethodAccess = ReflectionUtil.getMethodAccess(target.getClass());

        for (String methodName : srcMethodAccess.getMethodNames()) {
            if (StringUtils.startsWith(methodName, "get")) {
                String fieldName = StringUtils.uncapitalize(StringUtils.removeStart(methodName, "get"));
                int index;
                try {
                    index = targetMethodAccess.getIndex("set" + StringUtils.capitalize(fieldName));
                } catch (IllegalArgumentException e) {
                    continue;
                }
                Field srcField = ReflectionUtil.getAccessibleField(source, fieldName);
                Field targetField = ReflectionUtil.getAccessibleField(target, fieldName);
                if (srcField == null || targetField == null) {
                    continue;
                }
                org.shj.spring.boot.lang.MapperField srcMapperAnno = srcField.getAnnotation(org.shj.spring.boot.lang.MapperField.class);
                org.shj.spring.boot.lang.MapperField targetMapperAnno = targetField.getAnnotation(org.shj.spring.boot.lang.MapperField.class);
                if (srcMapperAnno != null && !srcMapperAnno.convertTo()) {
                    continue;
                }
                if (targetMapperAnno != null && !targetMapperAnno.convertFrom()) {
                    continue;
                }
                Object srcFieldValue = srcMethodAccess.invoke(source, methodName);
                Object targetFieldValue = converter.convert(srcFieldValue, srcField, targetField);
//                try {
//                    targetFieldValue = converter.convert(srcFieldValue, srcField, targetField);
//                } catch (Exception e) {
//                    log.error(String.format("Converter[class:%s] cannot convert src[%s][class:%s] to target[class:%s]", converter.getClass(), JSONObject.toJSONString(srcFieldValue), srcField.getType(), targetField.getType()));
//                }
                if (targetFieldValue == null && ClassUtil.isPrimaryClass(targetField.getType())) {
                    continue;
                }
                targetMethodAccess.invoke(target, index, targetFieldValue);
            }
        }

    }
}
